﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Gumini.JulaDB.Enums;

namespace Gumini.JulaDB.Provider
{
    [System.ComponentModel.DesignerCategory("")]
    public class JulaDbConnection : DbConnection
    {
        private const string DataSourceKey = "Data Source";
        private const string InMemoryDatabaseValue = ":memory:";
        private JulaDbEngine _database;
        private ConnectionState _connectionState = ConnectionState.Closed;
        private string _connectionString = "";
        private IDictionary<string, string> _connectionDetails;

        /// <summary>
        /// Initializes a new instance of the <see cref="JulaDbConnection"/> class.
        /// </summary>
        public JulaDbConnection()
            : this(string.Empty) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="JulaDbConnection"/> class.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        public JulaDbConnection(string connectionString)
        {
            this.ConnectionString = connectionString;
        }

        internal JulaDbEngine JulaDb
        {
            get { return _database; }
        }

        /// <summary>
        /// Parses the connection string.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        /// <returns></returns>
        private static IDictionary<string, string> ParseConnectionString(string connectionString)
        {
            var result = new Dictionary<string, string>();
            var items = connectionString.Split(';');
            foreach(var item in items)
            {
                var trimmed = item.Trim();
                if (trimmed.Length > 0)
                {
                    var pair = trimmed.Split('=');
                    if (pair.Length != 2)
                        throw new JulaDbException(ErrorCode.InvalidConnectionString, null, "The connection string has invalid format");
                    result.Add(pair.First().Trim(), pair.Last().Trim());
                }
            }
            return result;
        }

        /// <summary>
        /// Opens a database connection with the settings specified by the ConnectionString property of the provider-specific Connection object.
        /// </summary>
        public override void Open()
        {
            if (this.State != ConnectionState.Closed)
                throw new JulaDbException(ErrorCode.InvalidConnectionState, "Unable to open connection in state {0}", this.State);

            _connectionDetails = ParseConnectionString(this.ConnectionString);
            if (!_connectionDetails.ContainsKey(DataSourceKey))
                throw new JulaDbException(ErrorCode.InvalidConnectionString, "{0} must be specified", DataSourceKey);

            if (this.DataSource == InMemoryDatabaseValue)
                _database = JulaDbEngine.CreateInMemory();
            else
                _database = JulaDbEngine.OpenOrCreateFile(this.DataSource);

            _connectionState = ConnectionState.Open;
        }

        /// <summary>
        /// Closes the connection to the database.
        /// </summary>
        public override void Close()
        {
            if (this.State != ConnectionState.Open)
                throw new JulaDbException(ErrorCode.InvalidConnectionState, "Unable to close connection in state {0}", this.State);

            _database.Close();

            _connectionState = ConnectionState.Closed;
        }

        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
        {
            throw new NotSupportedException("Transactions are not supported");
        }

        public override void ChangeDatabase(string databaseName)
        {
            throw new NotSupportedException("Dynamic database change is not supported");
        }

        /// <summary>
        /// Gets or sets the string used to open a database.
        /// </summary>
        /// <returns>A string containing connection settings.</returns>
        public override string ConnectionString
        {
            get { return _connectionString; }
            set
            {
                if (_connectionState != ConnectionState.Closed)
                    throw new InvalidOperationException("Unable to set ConnectionString in current connection state");
                _connectionString = value;
            }
        }

        protected override DbCommand CreateDbCommand()
        {
            return new JulaDbCommand(this);
        }

        /// <summary>
        /// Gets the name of the database server to which to connect.
        /// </summary>
        /// <returns>The name of the database server to which to connect. The default value is an empty string.</returns>
        public override string DataSource
        {
            get
            {
                if (_connectionDetails.ContainsKey(DataSourceKey))
                    return _connectionDetails[DataSourceKey];
                else
                    return string.Empty;
            }
        }

        /// <summary>
        /// Gets the name of the current database after a connection is opened, or the database name specified in the connection string before the connection is opened.
        /// </summary>
        public override string Database
        {
            get { return string.Empty; }
        }

        /// <summary>
        /// Gets a string that represents the version of the server to which the object is connected.
        /// </summary>
        public override string ServerVersion
        {
            get { return JulaDbEngine.Version; }
        }

        /// <summary>
        /// Gets the current state of the connection.
        /// </summary>
        public override ConnectionState State
        {
            get { return _connectionState; }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (this.State == ConnectionState.Open)
                this.Close();
        }

        public override string ToString()
        {
            return String.Format("ConnectionString='{0}', State={1}", this.ConnectionString, this.State);
        }
    }
}
